1 /*
2  * Hunt - a framework for web and console application based on Collie using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.application.application;
13 
14 import collie.codec.http.server.websocket;
15 import kiss.container.ByteBuffer;
16 import collie.codec.http.server;
17 import collie.codec.http;
18 import collie.bootstrap.serversslconfig;
19 import collie.utils.exception;
20 import hunt.cache;
21 
22 public import kiss.event;
23 public import kiss.event.EventLoopGroup;
24 
25 public import std.socket;
26 public import kiss.logger;
27 public import std.file;
28 
29 import std.string;
30 import std.conv;
31 import std.stdio;
32 import std.uni;
33 import std.path;
34 import std.parallelism;
35 import std.exception;
36 
37 import hunt.init;
38 import hunt.routing;
39 import hunt.application.dispatcher;
40 import hunt.security.acl.Manager;
41 
42 public import hunt.http;
43 public import hunt.view;
44 public import hunt.i18n;
45 public import hunt.application.config;
46 public import hunt.application.middleware;
47 public import hunt.security.acl.Identity;
48 
49 
50 abstract class WebSocketFactory
51 {
52     IWebSocket newWebSocket(const HTTPMessage header);
53 };
54 
55 
56 final class Application
57 {
58     static @property Application getInstance()
59     {
60         if(_app is null)
61         {
62             _app = new Application();
63         }
64 
65         return _app;
66     }
67 
68     Address binded(){return addr;}
69 
70     /**
71      Add a Router rule
72      Params:
73      method =  the HTTP method. 
74      path   =  the request path.
75      handle =  the delegate that handle the request.
76      group  =  the rule's domain group.
77      */
78     auto addRoute(string method, string path, HandleFunction handle, string group = DEFAULT_ROUTE_GROUP)
79     {
80        logDebug(__FUNCTION__,method, path, handle, group);
81         this._dispatcher.router.addRoute(method, path, handle, group);
82 
83         return this;
84     }
85 
86 	Application GET(string path,HandleFunction handle)
87 	{
88         this._dispatcher.router.addRoute("GET", path, handle,DEFAULT_ROUTE_GROUP);
89 		return this;
90 	}
91 	Application POST(string path,HandleFunction handle)
92 	{
93         this._dispatcher.router.addRoute("POST", path, handle,DEFAULT_ROUTE_GROUP);
94 		return this;
95 	}
96 	Application DELETE(string path,HandleFunction handle)
97 	{
98         this._dispatcher.router.addRoute("DELETE", path, handle,DEFAULT_ROUTE_GROUP);
99 		return this;
100 	}
101 	Application PATCH(string path,HandleFunction handle)
102 	{
103         this._dispatcher.router.addRoute("PATCH", path, handle,DEFAULT_ROUTE_GROUP);
104 		return this;
105 	}
106 	Application PUT(string path,HandleFunction handle)
107 	{
108         this._dispatcher.router.addRoute("PUT", path, handle,DEFAULT_ROUTE_GROUP);
109 		return this;
110 	}
111 	Application HEAD(string path,HandleFunction handle)
112 	{
113         this._dispatcher.router.addRoute("HEAD", path, handle,DEFAULT_ROUTE_GROUP);
114 		return this;
115 	}
116 	Application OPTIONS(string path,HandleFunction handle)
117 	{
118         this._dispatcher.router.addRoute("OPTIONS", path, handle,DEFAULT_ROUTE_GROUP);
119 		return this;
120 	}
121 
122     // enable i18n
123     Application enableLocale(string resPath = DEFAULT_LANGUAGE_PATH, string defaultLocale = "en-us")
124     {
125         I18n i18n = I18n.instance();
126 
127         i18n.loadLangResources(resPath);
128         i18n.defaultLocale = defaultLocale;
129 
130         return this;
131     }
132 
133     void setWebSocketFactory(WebSocketFactory webfactory)
134     {
135         _wfactory = webfactory;
136     }
137 
138     version(NO_TASKPOOL){} else {
139         @property TaskPool taskPool(){return _tpool;}
140     }
141 
142     /// get the router.
143     @property router()
144     {
145         return this._dispatcher.router();
146     }
147 
148     @property server(){return _server;}
149 
150     @property mainLoop(){return _server.eventLoop;}
151 
152     @property loopGroup(){return _server.group;}
153 
154     @property AppConfig appConfig(){return Config.app;}
155 
156     void setCreateBuffer(CreatorBuffer cbuffer)
157     {
158         if(cbuffer)
159             _cbuffer = cbuffer;
160     }
161 
162     /*void setRedis(AppConfig.RedisConf conf)
163     {
164         version(USE_REDIS){
165             if(conf.enabled == true && conf.host && conf.port)
166             {
167                 conRedis.setDefaultHost(conf.host,conf.port,conf.password);    
168             }
169         }
170     }
171 
172     void setMemcache(AppConfig.MemcacheConf conf)
173     {
174         version(USE_MEMCACHE){
175             if(conf.enabled == true){
176                logDebug(conf);
177                 auto tmp1 = split(conf.servers,","); 
178                 auto tmp2 = split(tmp1[0],":"); 
179                 if(tmp2[0] && tmp2[1]){
180                     conMemcache.setDefaultHost(tmp2[0],tmp2[1].to!ushort);
181                 }
182             }
183         }
184     }*/
185 
186     private void initCache(AppConfig.CacheConf config)
187     {
188 		_manger.createCache("default" , config.storage , config.args , config.enableL2);
189 	}
190     
191     private void initSessionStorage(AppConfig.SessionConf config)
192     {
193 		_sessionStorage = new SessionStorage(UCache.CreateUCache(config.storage , config.args , false));
194       
195 		_sessionStorage.setPrefix(config.prefix);
196         _sessionStorage.setExpire(config.expire);
197     }
198 
199 	CacheManger getCacheManger()
200 	{
201 		return _manger;
202 	}
203 	
204 	SessionStorage getSessionStorage()
205 	{
206 		return _sessionStorage;
207 	}
208 	
209 	UCache getCache()
210 	{
211 		return  _manger.getCache("default");
212 
213 	}
214 
215 	AccessManager getAccessManager()
216 	{
217 		return _accessManager;
218 	}
219 
220     /**
221       Start the HTTPServer server , and block current thread.
222      */
223      void run()
224 	{
225 		start();
226 	}
227 
228 	/*
229 	void run(Address addr)
230 	{
231 		Config.app.http.address = addr.toAddrString;
232 		Config.app.http.port = addr.toPortString.to!ushort;
233 		setConfig(Config.app);
234 		start();
235 	}*/
236 
237 	void setConfig(AppConfig config)
238 	{
239 		setLogConfig(config.log);
240 		upConfig(config);
241 		//setRedis(config.redis);
242 		//setMemcache(config.memcache);
243 		initCache(config.cache);
244 		initSessionStorage(config.session);
245 	}
246 
247 	void start()
248 	{
249 		writeln("Try to open http://",addr.toString(),"/");
250 		_server.start();
251 	}
252 
253     /**
254       Stop the server.
255      */
256     void stop()
257     {
258         _server.stop();
259     }
260     private:
261     RequestHandler newHandler(RequestHandler, HTTPMessage msg){
262         if(!msg.upgraded)
263         {
264             return new Request(_cbuffer,&handleRequest,_maxBodySize);
265         }
266         else if(_wfactory)
267         {
268             return _wfactory.newWebSocket(msg);
269         }
270 
271         return null;
272     }
273 
274     Buffer defaultBuffer(HTTPMessage msg) nothrow
275     {
276         try{
277             import std.experimental.allocator.gc_allocator;
278             import kiss.container.ByteBuffer;
279             if(msg.chunked == false)
280             {
281                 string contign = msg.getHeaders.getSingleOrEmpty(HTTPHeaderCode.CONTENT_LENGTH);
282                 if(contign.length > 0)
283                 {
284                     import std.conv;
285                     uint len = 0;
286                     collectException(to!(uint)(contign),len);
287                     if(len > _maxBodySize)
288                         return null;
289                 }
290             }
291 
292             return new ByteBuffer!(GCAllocator)();
293         }
294         catch(Exception e)
295         {
296             showException(e);
297             return null;
298         }
299     }
300 
301     void handleRequest(Request req) nothrow
302     {
303         this._dispatcher.dispatch(req);
304     }
305 
306     private:
307     void upConfig(AppConfig conf)
308     {
309         _maxBodySize = conf.upload.maxSize;
310         version(NO_TASKPOOL)
311         {
312             // NOTHING
313         }
314         else
315         {
316             _tpool = new TaskPool(conf.http.workerThreads);
317             _tpool.isDaemon = true;
318         }
319 
320         HTTPServerOptions option = new HTTPServerOptions();
321         option.maxHeaderSize = conf.http.maxHeaderSize;
322         //option.listenBacklog = conf.http.listenBacklog;
323 
324         version(NO_TASKPOOL)
325         {
326             option.threads = conf.http.ioThreads + conf.http.workerThreads;
327         }
328         else
329         {
330             option.threads = conf.http.ioThreads;
331         }
332 
333         option.timeOut = conf.http.keepAliveTimeOut;
334         option.handlerFactories ~= (&newHandler);
335         _server = new HttpServer(option);
336         logDebug("addr:",conf.http.address, ":", conf.http.port);
337         addr = parseAddress(conf.http.address,conf.http.port);
338         HTTPServerOptions.IPConfig ipconf;
339         ipconf.address = addr;
340 
341         _server.addBind(ipconf);
342 
343         //if(conf.webSocketFactory)
344         //    _wfactory = conf.webSocketFactory;
345 
346        logDebug(conf.route.groups);
347 
348         version(NO_TASKPOOL)
349         {
350         }
351         else
352         {
353             this._dispatcher.setWorkers(_tpool);
354         }
355         // init dispatcer and routes
356         if (conf.route.groups)
357         {
358             import std.array : split;
359             import std.string : strip;
360 
361             string[] groupConfig;
362 
363             foreach (v; split(conf.route.groups, ','))
364             {
365                 groupConfig = split(v, ":");
366 
367                 if (groupConfig.length == 3 || groupConfig.length == 4)
368                 {
369                     string value = groupConfig[2];
370 
371                     if (groupConfig.length == 4)
372                     {
373                         if (std.conv.to!int(groupConfig[3]) > 0)
374                         {
375                             value ~= ":"~groupConfig[3];
376                         }
377                     }
378 
379                     this._dispatcher.addRouteGroup(strip(groupConfig[0]), strip(groupConfig[1]), strip(value));
380 
381                     continue;
382                 }
383 
384                 logWarningf("Group config format error ( %s ).", v);
385             }
386         }
387 
388         this._dispatcher.loadRouteGroups();
389     }
390 
391     void setLogConfig(ref AppConfig.LogConfig conf)
392     {
393 		int level = 0;
394         switch(conf.level)
395         {
396             case "all":
397 			case "trace":
398 			case "debug":
399 				level = 0;
400                 break;
401             case "critical":
402             case "error":
403 				level = 3;
404                 break;
405             case "fatal":
406 				level = 4;
407                 break;
408             case "info":
409 				level = 1;
410                 break;
411             case "warning":
412 				level = 2;
413                 break;
414             case "off":
415 				level = 5;
416                 break;
417 			default:
418 				level = 0;
419         }
420 		LogConf logconf;
421 		logconf.level = cast(LogLevel)level;
422 		logconf.disableConsole = conf.disableConsole;
423         if(!conf.file.empty)
424 		    logconf.fileName = buildPath(conf.path, conf.file);
425 		logconf.maxSize = conf.maxSize;
426 		logconf.maxNum = conf.maxNum;
427 
428 		logLoadConf(logconf);
429 
430     }
431 
432 
433 
434 
435     version(USE_KISS_RPC) {
436         import kissrpc.RpcManager;
437         public void startRpcService(T,A...)() {
438             if (Config.app.rpc.enabled == false)
439                 return;
440             string ip = Config.app.rpc.service.address;
441             ushort port = Config.app.rpc.service.port;
442             int threadNum = Config.app.rpc.service.workerThreads;
443             RpcManager.getInstance().startService!(T,A)(ip, port, threadNum);
444         }
445         public void startRpcClient(T)(string ip, ushort port, int threadNum = 1) {
446             if (Config.app.rpc.enabled == false)
447                 return;
448             RpcManager.getInstance().connectService!(T)(ip, port, threadNum);
449         }
450     }
451 
452     this()
453     {
454         _cbuffer = &defaultBuffer;
455 		_accessManager = new AccessManager();
456 		_manger = new CacheManger();
457 
458         this._dispatcher = new Dispatcher();
459 		setConfig(Config.app);
460     }
461 
462     __gshared static Application _app;
463 
464     private:
465     Address addr;
466     HttpServer _server;
467     WebSocketFactory _wfactory;
468     uint _maxBodySize;
469     CreatorBuffer _cbuffer;
470     Dispatcher _dispatcher;
471     CacheManger _manger;
472 	SessionStorage _sessionStorage;
473 	AccessManager  _accessManager;
474 
475     version(NO_TASKPOOL)
476     {
477         // NOTHING TODO
478     }
479     else
480     {
481         __gshared TaskPool _tpool;
482     }
483 }